/*
*    Qizx/open 3.0 beta
*
* This code is the open-source version of Qizx.
* Copyright (C) 2004-2009 Axyana Software -- All rights reserved.
*
* The contents of this file are subject to the Mozilla Public License 
*  Version 1.1 (the "License"); you may not use this file except in 
*  compliance with the License. You may obtain a copy of the License at
*  http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
*  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
*  for the specific language governing rights and limitations under the
*  License.
*
* The Initial Developer of the Original Code is Xavier Franc - Axyana Software.
*
*/
using System;
using Item = System.Qizx.Api.Item;
using Node = System.Qizx.Api.Node;
using IQName = System.Qizx.Xdm.IQName;
using XQName = System.Qizx.Xdm.XQName;
using SequenceType = System.Qizx.XQuery.SequenceType;
using XQItem = System.Qizx.XQuery.XQItem;
using XQItemType = System.Qizx.XQuery.XQItemType;
using XQType = System.Qizx.XQuery.XQType;
using XQTypeException = System.Qizx.XQuery.XQTypeException;
using XQValue = System.Qizx.XQuery.XQValue;
namespace System.Qizx.XQuery.Dt {

    public class JavaMapping {
        internal static System.Collections.Hashtable _classToType = new System.Collections.Hashtable();
        internal static System.Collections.ArrayList abstractTypes = new System.Collections.ArrayList();

        // As strange as it may seem, we use the full classname as a key in the
        // classToType map: this is to cope with classes loaded by a different
        // ClassLoader
        private static void classToType(System.Type classe, XQType type) {
            _classToType[classe.FullName] = type;
        }

        private static void mapAbstractClass(System.Type classe, XQType type) {
            classToType(classe, type);
            abstractTypes.Add(classe); // sequential matching
        }

        /// <summary> Gets the XQ type for a declared Java type (argument or return value).
        /// Use a simple lookup.
        /// </summary>
        public static XQItemType getItemType(System.Type classe) {
            string name = classe.FullName;
            SequenceType stype = (SequenceType)_classToType[name];
            if (stype == null)
                return new WrappedObjectType(classe);
            else
                return (XQItemType)stype.ItemType;
        }

        /// <summary> Gets the XQ type for a concrete Java type (to convert).
        /// Use a simple lookup, if it fails, try abstract class mappings.
        /// </summary>
        public static XQItemType matchingItemType(System.Type classe) {
            SequenceType stype = matchingType(classe);
            if (stype != null)
                return (XQItemType)stype.ItemType;
            return XQType.WRAPPED_OBJECT;
        }

        public static SequenceType matchingType(System.Type classe) {
            string name = classe.FullName; // yep
            SequenceType stype = (SequenceType)_classToType[name];
            if (stype == null)
                for (int i = 0, size = abstractTypes.Count; i < size; i++) {
                    System.Type aclass = (System.Type)abstractTypes[i];
                    if (aclass.IsAssignableFrom(classe)) {
                        // cannot be null:
                        stype = (SequenceType)_classToType[aclass.FullName];
                        break;
                    }
                }
            return stype;
        }

        // Type conversion with recognition of arrays and iterators/collections
        // By default, returns WRAPPED_OBJECT? for non-array types and 
        // item()* for array types.
        public static SequenceType getSequenceType(System.Type classe) {
            SequenceType stype = matchingType(classe);
            if (stype != null)
                return stype;
            if (classe.IsArray) {
                XQItemType itype = getItemType(classe.GetElementType());
                if (itype == null)
                    itype = XQType.ITEM;
                return itype.star;
            } else if (typeof(System.Collections.IEnumerator).IsAssignableFrom(classe) || typeof(System.Collections.ICollection).IsAssignableFrom(classe) || typeof(System.Collections.IEnumerator).IsAssignableFrom(classe)) {
                return XQType.ITEM.star;
            }
            // unknown non-array type:
            return XQType.WRAPPED_OBJECT.opt;
        }

        /// <summary> Convert Java object to XQuery item.
        /// If the type is not specified (null),it is inferred from the object;
        /// arrays and collections are not recognized, therefore converted to
        /// wrapped objects, not to sequences.
        /// </summary>
        /// <throws>  XQTypeException  </throws>
        public static XQItem convertToItem(System.Object objValue, XQItemType type) {
            if (objValue == null)
                return null;
            if (type == null) {
                type = matchingItemType(objValue.GetType());
            }
            return type.convertFromObject(objValue);
        }

        /// <summary> Convert Java object to XQuery item.
        /// If the type is not specified (null), it is inferred from the object;
        /// arrays and collections are recognized and converted to sequences.
        /// </summary>
        public static XQValue convertToSequence(System.Object objValue, SequenceType type) {
            if (objValue == null)
                return System.Qizx.XQuery.XQValues.Empty;
            if (type == null) {
                type = getSequenceType(objValue.GetType());
            }
            return type.itemType().convertFromArray(objValue);
        }

        public static bool isSequence(System.Object objValue) {
            if (objValue == null)
                return false;
            SequenceType type = getSequenceType(objValue.GetType());
            return type != null && XQType.isRepeatable(type.Occurrence);
        }
        static JavaMapping() {
            // Notice that we map java classes to *Sequence* types (for the mapping of
            // Java method arguments)
            {
                classToType(typeof(void), XQType.NONE.opt);
                classToType(typeof(System.String), XQType.STRING.opt);
                classToType(typeof(bool), XQType.BOOLEAN.one);
                classToType(typeof(System.Boolean), XQType.BOOLEAN.one);
                classToType(typeof(double), XQType.DOUBLE.one);
                classToType(typeof(System.Double), XQType.DOUBLE.one);
                classToType(typeof(float), XQType.FLOAT.one);
                classToType(typeof(System.Single), XQType.FLOAT.one);
                classToType(typeof(System.Decimal), XQType.DECIMAL.opt);
                classToType(typeof(System.Decimal), XQType.DECIMAL.opt);
                classToType(typeof(long), XQType.INTEGER.one);
                classToType(typeof(System.Int64), XQType.INTEGER.one);
                classToType(typeof(System.Int32), XQType.INT.one);
                classToType(typeof(int), XQType.INT.one);
                classToType(typeof(short), XQType.SHORT.one);
                classToType(typeof(System.Int16), XQType.SHORT.one);
                classToType(typeof(sbyte), XQType.BYTE.one);
                classToType(typeof(System.SByte), XQType.BYTE.one);
                classToType(typeof(char), XQType.CHAR.one);
                classToType(typeof(System.Char), XQType.CHAR.one);
                classToType(typeof(System.DateTime), XQType.DATE_TIME.opt);
                classToType(typeof(System.Globalization.Calendar), XQType.DATE_TIME.opt);
                classToType(typeof(IQName), XQType.QNAME.one);
                classToType(typeof(XQName), XQType.QNAME.one);

                // generic types:
                mapAbstractClass(typeof(Node), XQType.NODE.opt);
                mapAbstractClass(typeof(XQValue), XQType.ANY);
                XQItemType domNode = new DomNodeType(null);
                mapAbstractClass(typeof(System.Xml.XmlNode), new SequenceType(domNode, System.Qizx.Api.SequenceState.OCC_ZERO_OR_ONE));

                // arrays:
                classToType(typeof(System.String[]), XQType.STRING.star);
                classToType(typeof(Item[]), XQType.ITEM.star);
                classToType(typeof(bool[]), XQType.BOOLEAN.star);
                classToType(typeof(double[]), XQType.DOUBLE.star);
                classToType(typeof(float[]), XQType.FLOAT.star);
                classToType(typeof(long[]), XQType.INTEGER.star);
                classToType(typeof(int[]), XQType.INT.star);
                classToType(typeof(short[]), XQType.SHORT.star);
                classToType(typeof(sbyte[]), XQType.BYTE.star);
                classToType(typeof(char[]), XQType.INTEGER.star);

                mapAbstractClass(typeof(Node[]), XQType.NODE.star);
                mapAbstractClass(typeof(System.Xml.XmlNode[]), new SequenceType(domNode, System.Qizx.Api.SequenceState.OCC_ZERO_OR_MORE));

                // iterators and collections
                mapAbstractClass(typeof(System.Collections.ICollection), XQType.ITEM.star);
                mapAbstractClass(typeof(System.Collections.IEnumerator), XQType.ITEM.star);
                mapAbstractClass(typeof(System.Collections.IEnumerator), XQType.ITEM.star);
                mapAbstractClass(typeof(System.Collections.ArrayList), XQType.ITEM.star);
                mapAbstractClass(typeof(System.Collections.ArrayList), XQType.ITEM.star);
            }
        }
    }
}